package utils

import (
	"context"
	"encoding/json"
	"fmt"
	"git.mycaigou.com/ycg/go2sky"
	"git.mycaigou.com/ycg/go2sky/propagation"
	language_agent "git.mycaigou.com/ycg/go2sky/reporter/grpc/language-agent"
	"git.mycaigou.com/ycg/stark-extend/go2skyplugins"
	"git.myscrm.cn/golang/stark/v4"
	"github.com/parnurzeal/gorequest"
	"log"
	"net/http"
	"net/url"
	"strconv"
)

const (
	GO_REQUEST_DEBUG = "go_request_debug"
	COMPONENT_HttpClient = 5005
)

type HttpClientTool struct {
	response gorequest.Response
	body     []byte
	errs     []error
}

func New() *HttpClientTool {
	return &HttpClientTool{}
}

func getOperationName(r *http.Request) string {
	return fmt.Sprintf("%s", r.URL.Path)
}

// 链路追踪 before
func (s *HttpClientTool) TraceBefore(ctx context.Context, request *gorequest.SuperAgent) go2sky.Span {
	tracer, err := go2skyplugins.GetTracer()
	if err != nil {
		log.Printf("create tracer error %v \n", err)
		return nil
	}
	if tracer != nil {
		path := ""
		host := ""
		parsedURL, err := url.Parse(request.Url)
		if err != nil {
			fmt.Println("Error parsing URL:", err)
		} else {
			path = parsedURL.Path
			host = parsedURL.Host
		}
		span, err := tracer.CreateExitSpan(ctx, path, host, func(header string) error {
			request.Header.Set(propagation.Header, header)
			return nil
		})
		if err != nil {
			stark.Logger.Error(ctx, "CreateExitSpan失败", err)
			log.Printf("create span error %v \n", err)
			return nil
		}
		span.SetComponent(COMPONENT_HttpClient)
		span.SetSpanLayer(language_agent.SpanLayer_Http)
		span.Tag(go2sky.TagHTTPMethod, request.Method)
		span.Tag(go2sky.TagURL, request.Url)
		// 请求头
		if request.Header != nil {
			go2skyplugins.SpanLog(span, request.Header, "HttpHeader")
		}

		// 请求参数
		go2skyplugins.SpanLog(span, request.Data, "HttpRequestParam")
		return span
	}
	return nil
}

// 链路追踪 after
func (s *HttpClientTool) TraceAfter(ctx context.Context, span go2sky.Span, response gorequest.Response, responseBody []byte) {
	if span != nil {
		defer span.End()
		if response != nil {
			span.Tag(go2sky.TagStatusCode, strconv.Itoa(response.StatusCode))
			// 返回
			if response.StatusCode >= http.StatusBadRequest {
				go2skyplugins.SpanErrorHandler(newHttpResponseError(string(responseBody)), span, go2skyplugins.EVENT_HTTP_CLIENT, "")
			} else {
				go2skyplugins.SpanLog(span, responseBody, "HttpResponse")
			}
		}
	}
}

type httpResponseError struct {
	msg  string
}

func (e httpResponseError) Error() string {
	return e.msg
}

func newHttpResponseError(msg string) error {
	return httpResponseError {
		msg:  msg,
	}
}

// Post
// http client post request tool
func (s *HttpClientTool) Post(ctx context.Context, url string, params interface{}, headers map[string]string) *HttpClientTool {
	stark.Logger.Infof(ctx, "(http-client-post-before) http request url:%s ,header: %+v, params: %+v", url, headers, params)
	request := gorequest.New()

	reqDebug := stark.Configer.(stark.ApolloConfigerIface).GetStringValue(GO_REQUEST_DEBUG, "0")
	if reqDebug == "1" {
		request.SetDebug(true)
	}

	// default: application/json
	client := request.Post(url).Set("Content-Type", "application/json")
	// custom headers
	if headers != nil {
		for key, val := range headers {
			client.Set(key, val)
		}
	}

	if params != nil {
		client = client.Send(params)
	}
	span := s.TraceBefore(ctx, request)
	defer func() {
		s.TraceAfter(ctx, span, s.response, s.body)
	}()
	s.response, s.body, s.errs = client.EndBytes()

	stark.Logger.Infof(ctx, "(http-client-post-after) http request url:%s ,header: %+v, params: %+v, body: %+v, errs: %+v, response: %+v",
		url, headers, params, string(s.body), s.errs, s.response)
	return s
}

// Get
// http client get request tool
func (s *HttpClientTool) Get(ctx context.Context, url string, headers map[string]string) *HttpClientTool {
	stark.Logger.Infof(ctx, "(http-client-get-before) http request url:%s ,header: %+v", url, headers)
	request := gorequest.New()
	reqDebug := stark.Configer.(stark.ApolloConfigerIface).GetStringValue(GO_REQUEST_DEBUG, "0")
	if reqDebug == "1" {
		request.SetDebug(true)
	}
	client := request.Get(url)

	// custom headers
	if headers != nil {
		for key, val := range headers {
			client.Set(key, val)
		}
	}
	span := s.TraceBefore(ctx, request)
	defer func() {
		s.TraceAfter(ctx, span, s.response, s.body)
	}()
	s.response, s.body, s.errs = client.EndBytes()

	stark.Logger.Infof(ctx, "(http-client-get-after) http request url:%s ,header: %+v, body: %+v, errs: %+v, response: %+v",
		url, headers, string(s.body), s.errs, s.response)

	return s
}

// EndStruct
// convert interface(v) to struct
func (s *HttpClientTool) EndStruct(v interface{}) (gorequest.Response, []byte, []error) {
	resp, body, errs := s.response, s.body, s.errs
	if len(errs) > 0 {
		return nil, body, errs
	}

	err := json.Unmarshal(body, &v)
	if err != nil {
		s.errs = append(s.errs, err)
		return resp, body, s.errs
	}

	return resp, body, nil
}


// JoinQueryStr
// 拼接get请求query字符串
func JoinQueryStr(url string, params map[string]interface{}) string {
	//参数拼接
	if params != nil {
		queryStr := ""
		for key, vl := range params {
			if queryStr == "" {
				queryStr = fmt.Sprintf("%s=%v", key, vl)
			} else {
				queryStr = fmt.Sprintf("%s&%s=%v", queryStr, key, vl)
			}
		}

		url = fmt.Sprintf("%s?%s", url, queryStr)
	}

	return url
}
